/******************************************************************************* * Copyright (c) 2007, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Manumitting Technologies Inc - bug 488721 ******************************************************************************/ package org.eclipse.ui.internal.quickaccess; import org.eclipse.jface.resource.DeviceResourceException; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.graphics.TextStyle; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.internal.IWorkbenchGraphicConstants; import org.eclipse.ui.internal.WorkbenchImages; import org.eclipse.ui.internal.WorkbenchPlugin; class QuickAccessEntry { boolean firstInCategory; boolean lastInCategory; QuickAccessElement element; QuickAccessProvider provider; int[][] elementMatchRegions; int[][] providerMatchRegions; /** * Provides a rough indicator of how good of a match this entry was to its * filter. Lower values indicate better match quality. A value of 0 * indicates the filter string was an exact match to the label or that there * is no filter being applied. */ private int matchQuality; /** * Indicates the filter string was a perfect match to the label or there is * no filter applied * * @see #getMatchQuality() */ public static final int MATCH_PERFECT = 0; /** * Indicates this entry is very relevant for the filter string. Recommended * value for when the filter was found at the start of the element's label * or a complete case sensitive camel case match. * * @see #getMatchQuality() */ public static final int MATCH_EXCELLENT = 5; /** * Indicates this entry is relevant for the filter string. Recommended value * for when the complete filter was found somewhere inside the element's * label or provider. * * @see #getMatchQuality() */ public static final int MATCH_GOOD = 10; /** * Indicates only part of the filter string matches to the element's label. * * @see #getMatchQuality() */ public static final int MATCH_PARTIAL = 15; /** * Creates a new quick access entry from the given element and provider. If * no filter was used to match this entry the element/provider match regions * may be empty and the match quality should be {@link #MATCH_PERFECT} * * @param element * the element this entry will represent * @param provider * the provider that owns this entry * @param elementMatchRegions * list of text regions the filter string matched in the element * label, possibly empty * @param providerMatchRegions * list of text regions the filter string matches in the provider * label, possible empty * @param matchQuality * a rough indication of how closely the filter matched, lower * values indicate a better match. It is recommended to use the * constants available on this class: {@link #MATCH_PERFECT}, * {@link #MATCH_EXCELLENT}, {@link #MATCH_GOOD}, * {@link #MATCH_PARTIAL} */ QuickAccessEntry(QuickAccessElement element, QuickAccessProvider provider, int[][] elementMatchRegions, int[][] providerMatchRegions, int matchQuality) { this.element = element; this.provider = provider; this.elementMatchRegions = elementMatchRegions; this.providerMatchRegions = providerMatchRegions; this.matchQuality = matchQuality; } Image getImage(QuickAccessElement element, ResourceManager resourceManager) { Image image = findOrCreateImage(element.getImageDescriptor(), resourceManager); if (image == null) { image = WorkbenchImages .getImage(IWorkbenchGraphicConstants.IMG_OBJ_ELEMENT); } return image; } private Image findOrCreateImage(ImageDescriptor imageDescriptor, ResourceManager resourceManager) { if (imageDescriptor == null) { return null; } Image image = (Image) resourceManager.find(imageDescriptor); if (image == null) { try { image = resourceManager.createImage(imageDescriptor); } catch (DeviceResourceException e) { WorkbenchPlugin.log(e); } } return image; } public void measure(Event event, TextLayout textLayout, ResourceManager resourceManager, TextStyle boldStyle) { Table table = ((TableItem) event.item).getParent(); textLayout.setFont(table.getFont()); event.width = 0; switch (event.index) { case 0: if (firstInCategory || providerMatchRegions.length > 0) { textLayout.setText(provider.getName()); if (boldStyle != null) { for (int[] matchRegion : providerMatchRegions) { textLayout.setStyle(boldStyle, matchRegion[0], matchRegion[1]); } } } else { textLayout.setText(""); //$NON-NLS-1$ } break; case 1: textLayout.setText(element.getLabel()); // Two situations for measuring icons: // - command with very large icon image (500x500) [scale down] // - command with normal image (16x16) but small text-height (8pt) Image image = getImage(element, resourceManager); Rectangle imageRect = image.getBounds(); Rectangle textBounds = textLayout.getBounds(); int iconSize = imageRect.height; // Heuristic: only scale image if has double the pixels if (iconSize > 16 && iconSize >= 2 * textBounds.height) { // image will be scaled down to fit iconSize = textBounds.height; } // include additional line 1 for category separator event.height = Math.max(event.height, iconSize + 3); event.width += iconSize + 4; if (boldStyle != null) { for (int[] matchRegion : elementMatchRegions) { textLayout.setStyle(boldStyle, matchRegion[0], matchRegion[1]); } } break; } Rectangle rect = textLayout.getBounds(); event.width += rect.width + 4; event.height = Math.max(event.height, rect.height + 2); } public void paint(Event event, TextLayout textLayout, ResourceManager resourceManager, TextStyle boldStyle, Color grayColor) { final Table table = ((TableItem) event.item).getParent(); textLayout.setFont(table.getFont()); switch (event.index) { case 0: if (firstInCategory || providerMatchRegions.length > 0) { textLayout.setText(provider.getName()); if (boldStyle != null) { for (int[] matchRegion : providerMatchRegions) { textLayout.setStyle(boldStyle, matchRegion[0], matchRegion[1]); } } if (grayColor != null && providerMatchRegions.length > 0 && !firstInCategory) { event.gc.setForeground(grayColor); } Rectangle availableBounds = ((TableItem) event.item).getTextBounds(event.index); Rectangle requiredBounds = textLayout.getBounds(); textLayout.draw(event.gc, availableBounds.x + 1, availableBounds.y + (availableBounds.height - requiredBounds.height) / 2); } break; case 1: String label = element.getLabel(); if (element instanceof CommandElement) { CommandElement commandElement = (CommandElement) element; String binding = commandElement.getBinding(); if (binding != null) { StyledString styledString = StyledCellLabelProvider.styleDecoratedString(label, StyledString.QUALIFIER_STYLER, new StyledString(commandElement .getCommand())); StyleRange[] styleRanges = styledString.getStyleRanges(); for (StyleRange styleRange : styleRanges) { textLayout.setStyle(styleRange, styleRange.start, styleRange.start + styleRange.length); } } } // draw images to fit square area sized by the text area Image image = getImage(element, resourceManager); Rectangle availableBounds = ((TableItem) event.item).getTextBounds(event.index); Rectangle requiredBounds = textLayout.getBounds(); Rectangle imageBounds = image.getBounds(); // 3 = top + bottom + category lines int maxImageSize = availableBounds.height - 3; // preserve aspect ratio int destHeight = Math.min(imageBounds.height, maxImageSize); int destWidth = destHeight * imageBounds.width / imageBounds.height; // and centre within available space; remove 1 from height for // category separator int startX = (maxImageSize - destWidth) / 2; int startY = (availableBounds.height - 1 - destHeight) / 2; event.gc.drawImage(image, 0, 0, imageBounds.width, imageBounds.height, availableBounds.x + startX, availableBounds.y + startY, destWidth, destHeight); textLayout.setText(label); if (boldStyle != null) { for (int[] matchRegion : elementMatchRegions) { textLayout.setStyle(boldStyle, matchRegion[0], matchRegion[1]); } } textLayout.draw(event.gc, availableBounds.x + maxImageSize + 4, availableBounds.y + (availableBounds.height - requiredBounds.height) / 2); break; } if (lastInCategory) { if (grayColor != null) event.gc.setForeground(grayColor); Rectangle bounds = ((TableItem)event.item).getBounds(event.index); event.gc.drawLine(Math.max(0, bounds.x - 1), bounds.y + bounds.height - 1, bounds.x + bounds.width, bounds.y + bounds.height - 1); } } /** * @param event */ public void erase(Event event) { // We are only custom drawing the foreground. event.detail &= ~SWT.FOREGROUND; } /** * Provides a rough indicator of how good of a match this entry was to its * filter. Lower values indicate better match quality. A value of * {@link #MATCH_PERFECT} indicates the filter string was an exact match to * the label or that there is no filter being applied. * * @return Returns the match quality */ public int getMatchQuality() { return matchQuality; } }